package plp.orientadaObjetos2.util;

import plp.expressions2.expression.Id;
import plp.orientadaObjetos1.excecao.declaracao.ClasseNaoDeclaradaException;
import plp.orientadaObjetos1.util.Tipo;
import plp.orientadaObjetos1.util.TipoPrimitivo;
import plp.orientadaObjetos2.ambiente.Nomes;
import plp.orientadaObjetos2.memoria.AmbienteCompilacaoOO2;
import plp.orientadaObjetos2.memoria.AmbienteExecucaoOO2;
import plp.orientadaObjetos2.memoria.DefClasseOO2;

// TODO: Refatorar esta classe para minimizar clones de codigo
public class HierarquiaUtils {

	public static boolean emMesmaHierarquia(Tipo tipo, Tipo outroTipo, AmbienteCompilacaoOO2 ambiente) throws ClasseNaoDeclaradaException{
		boolean emMesmaHierarquia = false;
		if(tipo.equals(outroTipo)){
			emMesmaHierarquia = true;
		} else {
			emMesmaHierarquia = ehSubTipo(tipo, outroTipo, ambiente);
			if(!emMesmaHierarquia){
				emMesmaHierarquia = ehSubTipo(outroTipo, tipo, ambiente);
			}
		}
		return emMesmaHierarquia;
	}

	/**
	 * Retorna <code>true</code> se <code>tipo</code> eh um subtipo de <code>outroTipo</code>.
	 * Se <code>tipo == outroTipo</code> OU <code>tipo.equals(outroTipo)</code> forem <code>true</code>,
	 * este metodo retorna <code>false</code>.
	 * @param tipo
	 * @param outroTipo
	 * @param ambiente
	 * @return
	 * @throws ClasseNaoDeclaradaException
	 */
	public static boolean ehSubTipo(Tipo tipo, Tipo outroTipo, AmbienteCompilacaoOO2 ambiente) throws ClasseNaoDeclaradaException {
		if(tipo instanceof TipoPrimitivo || outroTipo instanceof TipoPrimitivo){
			return false;
		}
		DefClasseOO2 defClasse = (DefClasseOO2) ambiente.getDefClasse(tipo.getTipo());
		if(defClasse.isNativa() && outroTipo.getTipo().equals(new plp.orientadaObjetos1.expressao.leftExpression.Id(Nomes.CLASSE_RAIZ))){
			return true;
		}
		boolean ehSubTipo = false;
		Id idPai;
		SuperClasseMap superClasse = ambiente.getSuperClasse(tipo.getTipo());
		if(superClasse != null){
			idPai = superClasse.getSuperClasse();
			while(idPai != null){
				if(idPai.equals(outroTipo.getTipo())){
					ehSubTipo = true;
					break;
				}
				SuperClasseMap superClassePai = ambiente.getSuperClasse(idPai);
				if(superClassePai != null){
					idPai = superClassePai.getSuperClasse();
				} else {
					idPai = null;
				}
			}
		}
		return ehSubTipo;
	}

	public static boolean emMesmaHierarquia(DefClasseOO2 tipo, DefClasseOO2 outroTipo, AmbienteExecucaoOO2 ambiente) throws ClasseNaoDeclaradaException{
		boolean emMesmaHierarquia = false;
		
		if(tipo.getIdClasse().equals(outroTipo.getIdClasse())){
			emMesmaHierarquia = true;
		} else {
			emMesmaHierarquia = ehSubTipo(tipo, outroTipo, ambiente);
			if(!emMesmaHierarquia){
				emMesmaHierarquia = ehSubTipo(outroTipo, tipo, ambiente);
			}
		}
		return emMesmaHierarquia;
	}

	public static boolean ehSubTipo(DefClasseOO2 descendente, DefClasseOO2 possivelAncestral, AmbienteExecucaoOO2 ambiente) throws ClasseNaoDeclaradaException {
		if(descendente.isNativa() && possivelAncestral.getIdClasse().equals(new plp.orientadaObjetos1.expressao.leftExpression.Id(Nomes.CLASSE_RAIZ))){
			return true;
		}
		boolean ehSubTipo = false;
		Id idPai;
		idPai = descendente.getNomeSuperClasse();
		while(idPai != null){
			if(idPai.equals(possivelAncestral.getIdClasse())){
				ehSubTipo = true;
				break;
			}
			SuperClasseMap superClassePai = ambiente.getSuperClasse(idPai);
			if(superClassePai != null){
				idPai = superClassePai.getSuperClasse();
			} else {
				idPai = null;
			}
		}
		return ehSubTipo;
	}
}
